﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Modbus.Device;
using Modbus.Utility;

namespace Modbus.Extensions.Enron
{
	/// <summary>
	/// Utility extensions for the Enron Modbus dialect.
	/// </summary>
	public static class EnronModbus
	{
		/// <summary>
		/// Read contiguous block of 32 bit holding registers.
		/// </summary>
		/// <param name="master">The Modbus master.</param>
		/// <param name="slaveAddress">Address of device to read values from.</param>
		/// <param name="startAddress">Address to begin reading.</param>
		/// <param name="numberOfPoints">Number of holding registers to read.</param>
		/// <returns>Holding registers status</returns>
		public static uint[] ReadHoldingRegisters32(this ModbusMaster master, byte slaveAddress, ushort startAddress, ushort numberOfPoints)
		{
			if (master == null)
				throw new ArgumentNullException("master");
			ValidateNumberOfPoints(numberOfPoints, 62);

			// read 16 bit chunks and perform conversion
			var rawRegisters = master.ReadHoldingRegisters(slaveAddress, startAddress, (ushort) (numberOfPoints * 2));

			return Convert(rawRegisters).ToArray();
		}

		/// <summary>
		/// Read contiguous block of 32 bit input registers.
		/// </summary>
		/// <param name="master">The Modbus master.</param>
		/// <param name="slaveAddress">Address of device to read values from.</param>
		/// <param name="startAddress">Address to begin reading.</param>
		/// <param name="numberOfPoints">Number of holding registers to read.</param>
		/// <returns>Input registers status</returns>
		public static uint[] ReadInputRegisters32(this ModbusMaster master, byte slaveAddress, ushort startAddress, ushort numberOfPoints)
		{
			if (master == null)
				throw new ArgumentNullException("master");
			ValidateNumberOfPoints(numberOfPoints, 62);

			var rawRegisters = master.ReadInputRegisters(slaveAddress, startAddress, (ushort) (numberOfPoints * 2));

			return Convert(rawRegisters).ToArray();
		}

		/// <summary>
		/// Write a single 16 bit holding register.
		/// </summary>
		/// <param name="master">The Modbus master.</param>
		/// <param name="slaveAddress">Address of the device to write to.</param>
		/// <param name="registerAddress">Address to write.</param>
		/// <param name="value">Value to write.</param>
		public static void WriteSingleRegister32(this ModbusMaster master, byte slaveAddress, ushort registerAddress, uint value)
		{
			if (master == null)
				throw new ArgumentNullException("master");

			master.WriteMultipleRegisters32(slaveAddress, registerAddress, new[] { value });
		}

		/// <summary>
		/// Write a block of contiguous 32 bit holding registers.
		/// </summary>
		/// <param name="master">The Modbus master.</param>
		/// <param name="slaveAddress">Address of the device to write to.</param>
		/// <param name="startAddress">Address to begin writing values.</param>
		/// <param name="data">Values to write.</param>
		public static void WriteMultipleRegisters32(this ModbusMaster master, byte slaveAddress, ushort startAddress, uint[] data)
		{
			if (master == null)
				throw new ArgumentNullException("master");
			if (data == null)
				throw new ArgumentNullException("data");

			if (data.Length == 0 || data.Length > 61)
			{
				throw new ArgumentException(String.Format(CultureInfo.InvariantCulture,
					"The length of argument data must be between 1 and 61 inclusive."));
			}

			master.WriteMultipleRegisters(slaveAddress, startAddress, Convert(data).ToArray());
		}

		/// <summary>
		/// Convert the 32 bit registers to two 16 bit values.
		/// </summary>
		internal static IEnumerable<ushort> Convert(uint[] registers)
		{
			foreach (var register in registers)
			{
				// low order value
				yield return BitConverter.ToUInt16(BitConverter.GetBytes(register), 0);

				// high order value
				yield return BitConverter.ToUInt16(BitConverter.GetBytes(register), 2);
			}
		}

		/// <summary>
		/// Convert the 16 bit registers to 32 bit registers.
		/// </summary>
		internal static IEnumerable<uint> Convert(ushort[] registers)
		{
			for (int i = 0; i < registers.Length; i++)
			{
				yield return ModbusUtility.GetUInt32(registers[i + 1], registers[i]);
				i++;
			}
		}

		internal static void ValidateNumberOfPoints(ushort numberOfPoints, ushort maxNumberOfPoints)
		{
			if (numberOfPoints < 1 || numberOfPoints > maxNumberOfPoints)
			{
				throw new ArgumentException(String.Format(CultureInfo.InvariantCulture,
					"Argument numberOfPoints must be between 1 and {0} inclusive.",
					maxNumberOfPoints));
			}
		}
	}
}
